package com.qd.core.service.login;

import com.cdqidi.exception.ApiException;
import com.cdqidi.util.RedisTemplateUtil;
import com.cdqidi.util.SpringContextHolder;
import com.qd.common.sys.constant.Constants;
import com.qd.common.sys.domain.entity.login.LoginUserDTO;
import com.qd.common.sys.domain.entity.login.LoginUserOrgDTO;
import com.qd.common.sys.domain.login.LoginBody;
import com.qd.common.sys.domain.login.LoginLogDTO;
import com.qd.common.sys.domain.login.LoginStatusDTO;
import com.qd.common.sys.domain.login.LoginUser;
import com.qd.common.sys.event.LoginStatusEvent;
import com.qd.common.sys.event.LoginUserLogEvent;
import com.qd.common.sys.event.LoginUserOrgEvent;
import com.qd.system.config.AdminConfig;
import com.qd.system.model.LoginUserOrg;
import com.qd.system.service.login.LoginUserOrgService;
import com.qd.system.service.org.OrgService;
import com.qd.system.service.person.AuthTokenService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import java.text.MessageFormat;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.TimeUnit;

@Component
@RequiredArgsConstructor
@Slf4j
public class LoginHolderService {
    private static final String PREFIX_ID = "login_user_password_userName_{0}";
    private final AuthenticationManager authenticationManager;
    private final AuthTokenService tokenService;
    private final StringRedisTemplate stringRedisTemplate;
    private final AdminConfig adminConfig;
    private final LoginUserOrgService loginUserOrgService;
    private final OrgService orgService;

    public Map<String, Object> login(LoginBody loginBody, AbstractAuthenticationToken authenticationToken) {
        String message = null;
        LoginUser loginUser = null;
        String status = Constants.LOGIN_FAIL;
        try {
            try {
                // 该方法会去调用UserDetailsServiceImpl.loadUserByUsername
                final Authentication authentication = authenticationManager
                        .authenticate(authenticationToken);
                loginUser = (LoginUser) authentication.getPrincipal();
                tokenService.setUserAgent(loginUser);
                if (loginUser.getUser().getState().equals(Constants.STATUS_DISABLE)) {
                    throw new ApiException("账户已停用");
                }
                // 生成token
                status = Constants.LOGIN_SUCCESS;
                message = "登录成功";
                loginUser.setRemote(loginBody.getRemote());
                buildCurrentOrg(loginBody, loginUser);
                final String token = tokenService.createToken(loginUser);
                boolean isUpdatePassword = false;
                if (adminConfig.isUpdatePassword()) {
                    if (loginBody.getPassword().equals(adminConfig.getDefaultPassword())) {
                        isUpdatePassword = true;
                    }
                }
                final Map<String, Object> map = new HashMap<>(2);
                map.put("token", token);
                map.put("isUpdatePassword", isUpdatePassword);
                deleteErrCountKey(loginUser.getUsername());
                return map;
            } catch (UsernameNotFoundException e) {
                message = "用户名不存在";
                throw new ApiException(e.getMessage());
            } catch (BadCredentialsException e) {
                message = "用户名密码错误";
                buildPasswordErr(loginBody);
            } catch (AuthenticationException e) {
                message = "其它错误";
                throw new ApiException(e);
            }
        } finally {
            saveLoginLog(loginBody, message, loginUser, status);
        }

        return Collections.emptyMap();
    }

    /**
     * 设置当前用户登录组织
     *
     * @param loginBody 登录参数
     * @param loginUser 登录信息
     */
    private void buildCurrentOrg(LoginBody loginBody, LoginUser loginUser) {
        LoginUserOrg loginUserOrg = loginUserOrgService.getUniqueByPersonIdAndRemote(loginUser.getUserId(), loginBody.getRemote());
        if (null == loginUserOrg) {
            final LoginUserOrgDTO userOrg = new LoginUserOrgDTO();
            userOrg.setPersonId(loginUser.getUserId());
            userOrg.setOrgId(loginUser.getCurrentOrgId());
            userOrg.setRemote(loginBody.getRemote());
            SpringContextHolder.publishEvent(new LoginUserOrgEvent(userOrg));
        } else {
            if (!loginUser.getCurrentOrgId().equals(loginUserOrg.getOrgId())) {
                final LoginUserOrgDTO userOrg = new LoginUserOrgDTO();
                userOrg.setPersonId(loginUser.getUserId());
                userOrg.setOrgId(loginUserOrg.getOrgId());
                userOrg.setRemote(loginBody.getRemote());
                SpringContextHolder.publishEvent(new LoginUserOrgEvent(userOrg));

                Optional.ofNullable(orgService.getById(loginUserOrg.getOrgId())).ifPresent(org -> {
                    loginUser.setCurrentOrgId(org.getOrgId());
                    loginUser.getUser().setOrgId(org.getOrgId());
                    loginUser.getUser().setOrgIdDisplay(org.getOrgName());
                });
            }
        }
    }

    /**
     * 保存登录日志
     *
     * @param loginBody 登录信息
     * @param message   成功或者失败信息
     * @param loginUser 登录用户
     * @param status    状态
     */
    private void saveLoginLog(LoginBody loginBody, String message, LoginUser loginUser, String status) {
        if (null == loginUser) {
            loginUser = new LoginUser();
            LoginUserDTO LoginUserDTO = new LoginUserDTO();
            LoginUserDTO.setAccount(loginBody.getUsername());
            loginUser.setUser(LoginUserDTO);
            tokenService.setUserAgent(loginUser);
        }
        LoginLogDTO loginLogDto = new LoginLogDTO(loginUser, status, message);
        SpringContextHolder.publishEvent(new LoginUserLogEvent(loginLogDto));
    }

    /**
     * 密码错误处理，只能重试多少次
     *
     * @param loginBody 登录信息
     */
    private void buildPasswordErr(LoginBody loginBody) {
        final String key = RedisTemplateUtil.buildKey(MessageFormat.format(PREFIX_ID, loginBody.getUsername()), true);
        String strCount = stringRedisTemplate.opsForValue().get(key);
        int count = 0;
        if (StringUtils.hasLength(strCount)) {
            count = Integer.parseInt(strCount);
        }
        count++;
        if (count <= adminConfig.getLoginErrCount()) {
            stringRedisTemplate.opsForValue().set(key, String.valueOf(count), 30, TimeUnit.MINUTES);
        }
        if (count == adminConfig.getLoginErrCount()) {
            SpringContextHolder.publishEvent(new LoginStatusEvent(new LoginStatusDTO(loginBody.getUsername(), Constants.STATUS_DISABLE)));
        }
        if (count >= adminConfig.getLoginErrCount()) {
            throw new ApiException("用户名密码连续" + adminConfig.getLoginErrCount() + "次输入错误，账户锁定");
        }
        throw new ApiException("用户名密码错误,重试次数" + (adminConfig.getLoginErrCount() - count) + "次");
    }


    private void deleteErrCountKey(String userName) {
        final String key = RedisTemplateUtil.buildKey(MessageFormat.format(PREFIX_ID, userName), true);
        stringRedisTemplate.delete(key);
    }
}
